package il.ac.technion.wiki.api;

import il.ac.technion.wiki.api.expressions.Expression;
import il.ac.technion.wiki.api.parsers.IQueryParser;
import il.ac.technion.wiki.api.search.Hit;
import il.ac.technion.wiki.api.search.IndexSearcher;
import il.ac.technion.wiki.api.wikidocument.IWikiDocument;
import il.ac.technion.wiki.impl.parsers.WikiQueryParser;
import il.ac.technion.wiki.impl.search.WikiIndexSearcher;

import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 *	A Wikipedia interface that allows you to add, remove or update Wiki Documents. 
 *	You can also search a docuemnt, or get a specific document if you know the docuemnt's title.   
 *	Implementation issues: The Wikipidia is implemented with the Singleton pattern.
 *	The default queryParser is set to WikiQueryParser, and the default IndexSearcher is set to WikiIndexSearcher.
 *	Both can be changed with the setQueryPaerser and setIndexSearcher methods, or with concrete parameters in the Constructor 
 */
public enum Wikipedia {
	INSTANCE;
	
	private SortedMap<String, IWikiDocument> documents = new TreeMap<String, IWikiDocument>(); // on memory DB alphabetically by the title
	private IQueryParser parser = null;
	IndexSearcher iSearcher = null;
	private final int MAX_HITS = 10;
	
	Wikipedia() {
		parser = new WikiQueryParser();
		iSearcher = new WikiIndexSearcher();
	}
	
	Wikipedia(IQueryParser parser, IndexSearcher iSearcher){
		if ((parser == null) || (iSearcher == null))
			throw new IllegalArgumentException("null parameter");
		this.parser = parser;
		this.iSearcher = iSearcher;
	}
	
	// Configuration methods
	public void setIndexSearcher(IndexSearcher toSet){
		if (toSet == null)
			throw new IllegalArgumentException("null parameter");
		this.iSearcher = toSet;
	}
	
	public void setQueryParser(IQueryParser toSet){
		if (toSet == null)
			throw new IllegalArgumentException("null parameter");
		parser = toSet;
	}
	
	// Document getters and manipulators
	public IWikiDocument getDocumentByTitle(String title){
		if (title == null)
			throw new IllegalArgumentException("title cannot be null");
		if (title == "")
			throw new IllegalArgumentException("title cannot be empty");
		return documents.get(title);
	}
	
	public void addDocument(IWikiDocument toAdd){
		if (toAdd == null)
			throw new IllegalArgumentException("null parameter");
		if (documents.containsKey(toAdd.getTitle()))
			throw new IllegalArgumentException("A document with this name already exists");
		documents.put(toAdd.getTitle(), toAdd);
		iSearcher.addDocument(toAdd);
	}
	
	/**
	 * Update a document if its title already exists, add a new one if not exists
	 */
	public void updateDocument(IWikiDocument toUpdate){
		if (toUpdate == null)
			throw new IllegalArgumentException("null parameter");
		IWikiDocument updated = documents.put(toUpdate.getTitle(), toUpdate);
		if (updated != null)
			iSearcher.removeDocument(updated);
		iSearcher.addDocument(toUpdate);
	}
	
	/**
	 * Removing the document associated with this title
	 */
	public void removeDocument(String title){
		if (title == null)
			throw new IllegalArgumentException("null parameter");
		if (title == "")
			throw new IllegalArgumentException("title cannot be empty");
		IWikiDocument removed = documents.remove(title);
		if (removed != null) 
			iSearcher.removeDocument(removed);
	}
	
	/**
	 * Searching documents with query
	 * @param query - should be given with the following convention: 
	 * "terms" should be written inside quotes, 
	 * operators should be written without quotes, 
	 * () parenthesis should be closed properly
	 */
	public List<IWikiDocument> Search(String query){
		if (query == null)
			throw new IllegalArgumentException("null parameter");
		Expression ex = parser.parse(query);
		List<Hit> hits = ex.getHits(iSearcher);
		List<IWikiDocument> $ = new LinkedList<IWikiDocument>();
		for (int i = 0 ; i < hits.size() && i < MAX_HITS; ++i){
			IWikiDocument currDoc = documents.get(hits.get(i).getTitle());
			if (currDoc != null)
				$.add(currDoc);
		}
		return $;
	}
}
